/**
 * 文 件 名:  ScriptUtil.java
 * 工 程 名:  ScriptManager
 * 创建日期:  2015年2月3日 下午4:48:29
 * 创建作者:  杨 强  <281455776@qq.com>
 */
package util;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import loader.FileSystemClassLoader;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import script.IBaseScript;
import script.IEventScript;

/**
 * 脚本工具
 * 
 * @author 杨 强
 *
 */
public class ScriptUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(ScriptUtil.class);
    /**
     * 脚本存放路径
     */
    private static String scriptRootPath = System.getProperty("user.dir");
    /**
     * 打包的脚本jar文件名称
     */
    private static String scriptJarFileName = "script.jar";
    /**
     * 脚本是否在mainScript中,true的时候在主脚本中，false的时候在副脚本中，这个变量控制脚本存储的位置
     */
    private static volatile boolean inMainScript = true;
    /**
     * 所有加载的脚本(主存储)
     */
    private static Map<String, IBaseScript> mainScripts = new ConcurrentHashMap<String, IBaseScript>();
    /**
     * 所有加载的事件以及对该事件感兴趣的脚本<事件名称,<脚本名称,脚本>>(主存储)
     */
    private static Map<String, Map<String, IBaseScript>> mainAllEventScripts = new ConcurrentHashMap<String, Map<String, IBaseScript>>();
    /**
     * 所有加载的脚本(副存储)
     */
    private static Map<String, IBaseScript> viceScripts = new ConcurrentHashMap<String, IBaseScript>();
    /**
     * 所有加载的事件以及对该事件感兴趣的脚本<事件名称,<脚本名称,脚本>>(副存储)
     */
    private static Map<String, Map<String, IBaseScript>> viceAllEventScripts = new ConcurrentHashMap<String, Map<String, IBaseScript>>();

    /**
     * 获取脚本根路径
     * 
     * @return scriptRootPath
     */
    public static String getScriptRootPath() {
        return scriptRootPath;
    }

    /**
     * 设置脚本根路径
     * 
     * @param scriptRootPath
     */
    public static void setScriptRootPath(String scriptRootPath) {
        ScriptUtil.scriptRootPath = scriptRootPath;
    }

    /**
     * 获取打包的脚本jar文件名称
     * 
     * @return scriptJarFileName
     */
    public static String getScriptJarFileName() {
        return scriptJarFileName;
    }

    /**
     * 设置打包的脚本jar文件名称
     * 
     * @param scriptJarFileName
     */
    public static void setScriptJarFileName(String scriptJarFileName) {
        ScriptUtil.scriptJarFileName = scriptJarFileName;
    }

    /**
     * 加载脚本
     * 
     * @param script
     *            要加载的脚本
     * @param tempScripts
     *            脚本存放地址
     * @param tempAllEventScripts
     *            事件以及对应的脚本存放地址
     * @param callLoad
     *            是否回调加载函数
     * @return 成功或失败
     */
    private static <T extends IBaseScript> boolean loadScript(T script, Map<String, IBaseScript> tempScripts, Map<String, Map<String, IBaseScript>> tempAllEventScripts, boolean callLoad) {
        if (script == null) {
            return false;
        }

        String scriptName = script.getName();
        // 脚本已经被加载
        if (tempScripts.containsKey(scriptName)) {
            return false;
        }

        // 放入加载列表
        tempScripts.put(scriptName, script);

        // 脚本需要触发的所有事件
        List<String> events = script.getEventList();
        for (String event : events) {
            Map<String, IBaseScript> eventScripts = getEventScriptMap(event, tempAllEventScripts);

            // 放入事件列表，外部保证所有的脚本不会同名
            eventScripts.put(scriptName, script);
        }
        if (callLoad) {
            LOGGER.info("脚本【{}】加载onloadScript回调", scriptName);
            // 加载后回调初始化函数
            script.onloadScript();
        }
        return true;
    }

    /**
     * 加载脚本
     * 
     * @param script
     *            要加载的脚本
     * @param callLoad
     *            是否回调加载函数
     * @return 成功或失败
     */
    private static <T extends IBaseScript> boolean loadScript(T script, boolean callLoad) {
        if (inMainScript) {
            return loadScript(script, mainScripts, mainAllEventScripts, callLoad);
        } else {
            return loadScript(script, viceScripts, viceAllEventScripts, callLoad);
        }
    }

    /**
     * 加载脚本
     * 
     * @param script
     *            要加载的脚本
     * @return 成功或失败
     */
    public static <T extends IBaseScript> boolean loadScript(T script) {
        return loadScript(script, true);
    }

    /**
     * 卸载脚本
     * 
     * @param script
     *            要卸载的脚本,不要求是原脚本,新的同名脚本同样适用
     * @param tempScripts
     *            脚本存放地址
     * @param tempAllEventScripts
     *            事件以及对应的脚本存放地址
     * @param callUnload
     *            是否回调卸载函数
     * @return 旧脚本
     */
    @SuppressWarnings("unchecked")
    private static <T extends IBaseScript> T unloadScript(T script, Map<String, IBaseScript> tempScripts, Map<String, Map<String, IBaseScript>> tempAllEventScripts, boolean callUnload) {
        if (script == null) {
            return null;
        }

        String scriptName = script.getName();
        // 脚本未加载，则不需要卸载
        if (!tempScripts.containsKey(scriptName)) {
            return null;
        }

        // 干掉老脚本，取老脚本加载的事件列表
        IBaseScript oldScript = tempScripts.remove(scriptName);
        List<String> events = oldScript.getEventList();
        for (String event : events) {
            Map<String, IBaseScript> eventScripts = getEventScriptMap(event, tempAllEventScripts);

            // 从事件列表中删除
            eventScripts.remove(scriptName);
        }
        if (callUnload) {
            LOGGER.info("脚本【{}】卸载unloadScript回调", scriptName);
            // 卸载后回调函数
            oldScript.unloadScript();
        }
        return (T) oldScript;
    }

    /**
     * 卸载脚本
     * 
     * @param script
     *            要卸载的脚本,不要求是原脚本,新的同名脚本同样适用
     * @param callUnload
     *            是否回调卸载函数
     * @return 旧脚本
     */
    private static <T extends IBaseScript> T unloadScript(T script, boolean callUnload) {
        if (inMainScript) {
            return unloadScript(script, mainScripts, mainAllEventScripts, callUnload);
        } else {
            return unloadScript(script, viceScripts, viceAllEventScripts, callUnload);
        }
    }

    /**
     * 卸载脚本
     * 
     * @param script
     *            要卸载的脚本,不要求是原脚本,新的同名脚本同样适用
     * @return 旧脚本
     */
    public static <T extends IBaseScript> T unloadScript(T script) {
        return unloadScript(script, true);
    }

    /**
     * 获取指定的事件脚本表
     * 
     * @param eventName
     *            事件全称
     * @param tempAllEventScripts
     *            事件以及对应的脚本存放地址
     * @return 对应事件的脚本表
     */
    private static Map<String, IBaseScript> getEventScriptMap(String eventName, Map<String, Map<String, IBaseScript>> tempAllEventScripts) {
        Map<String, IBaseScript> eventScriptMap = tempAllEventScripts.get(eventName);
        if (eventScriptMap == null) {
            eventScriptMap = new ConcurrentHashMap<String, IBaseScript>();
            tempAllEventScripts.put(eventName, eventScriptMap);
        }
        return eventScriptMap;
    }

    /**
     * 根据指定的事件获取所有监听的事件脚本
     * 
     * @param event
     *            事件
     * @param tempAllEventScripts
     *            事件以及对应的脚本存放地址
     * @return 对应事件的脚本列表
     */
    @SuppressWarnings("unchecked")
    private static <T extends IEventScript> List<T> getEventScripts(Class<T> event, Map<String, Map<String, IBaseScript>> tempAllEventScripts) {
        List<T> eventScripts = new ArrayList<T>();
        if (event != null) {
            Map<String, IBaseScript> eventScriptMap = getEventScriptMap(event.getName(), tempAllEventScripts);
            Iterator<Map.Entry<String, IBaseScript>> it = eventScriptMap.entrySet().iterator();
            while (it.hasNext()) {
                IBaseScript script = it.next().getValue();
                if (event.isInstance(script)) {// 这里可以不用判断
                    eventScripts.add((T) script);
                }
            }
        }
        return Collections.unmodifiableList(eventScripts);
    }

    /**
     * 根据指定的事件获取所有监听的事件脚本
     * 
     * @param event
     *            事件
     * @return 对应事件的脚本列表
     */
    public static <T extends IEventScript> List<T> getEventScripts(Class<T> event) {
        if (inMainScript) {
            return getEventScripts(event, mainAllEventScripts);
        } else {
            return getEventScripts(event, viceAllEventScripts);
        }
    }

    /**
     * 获取指定的已经加载的脚本
     * <p>
     * <br>
     * 脚本不能直接强转为特定类型T，建议直接强转为指定的接口类型，因为T可能是由不同的类加载器加载
     * 
     * @param cls
     *            脚本类
     * @return 对应的加载脚本
     */
    public static <T extends IBaseScript> IBaseScript getLoadedScript(Class<T> cls) {
        if (inMainScript) {
            return mainScripts.get(cls.getName());
        } else {
            return viceScripts.get(cls.getName());
        }
    }

    /**
     * 获取所有加载的脚本(包括带事件和不带事件的脚本)
     * 
     * @return 所有加载的脚本
     */
    public static List<IBaseScript> getLoadedScripts() {
        List<IBaseScript> tempScripts = new ArrayList<IBaseScript>();
        if (inMainScript) {
            tempScripts.addAll(mainScripts.values());
        } else {
            tempScripts.addAll(viceScripts.values());
        }
        return Collections.unmodifiableList(tempScripts);
    }

    /**
     * 获取所有的事件脚本，未带有事件的脚本不在里面
     * 
     * @return 所有的带有事件的加载脚本
     */
    public Map<String, Map<String, IBaseScript>> getAllEventScripts() {
        if (inMainScript) {
            return Collections.unmodifiableMap(mainAllEventScripts);
        } else {
            return Collections.unmodifiableMap(viceAllEventScripts);
        }
    }

    /**
     * 触发指定的事件脚本，该函数通常只有在事件只有一个确定的方法时才会被调用，否则该函数会调用事件中所有的方法
     * 
     * @param event
     *            事件类
     * @param params
     *            方法参数
     */
    public static <T extends IEventScript> void triggerEvent(Class<T> event, Object... params) {
        LOGGER.debug("开始触发事件【{}】", event.getName());
        List<T> eventScripts = getEventScripts(event);
        if (eventScripts.isEmpty()) {
            LOGGER.warn("触发事件【{}】失败，无脚本", event.getName());
            return;
        }

        // 获取该事件所有的方法
        Method[] methods = event.getDeclaredMethods();
        if (methods == null || methods.length < 1) {
            LOGGER.warn("触发事件【{}】失败，无方法", event.getName());
            return;
        }
        for (T script : eventScripts) {
            for (Method method : methods) {
                ReflectUtil.invokeMethod(script, method, params);
            }
        }
        LOGGER.debug("结束触发事件【{}】", event.getName());
    }

    /**
     * 重载指定名称的脚本
     * 
     * @param scriptName
     */
    @SuppressWarnings("unchecked")
    public static synchronized boolean reloadScript(String scriptName) {
        if (scriptName == null || scriptName.trim().isEmpty()) {
            return false;
        }
        LOGGER.info("重载脚本【{}】开始", scriptName);
        FileSystemClassLoader loader = new FileSystemClassLoader(scriptRootPath, scriptJarFileName);
        loader.addDirectLoadClass(scriptName);
        try {
            Class<IBaseScript> clz = (Class<IBaseScript>) loader.loadClass(scriptName);
            IBaseScript newScript = clz.newInstance();
            IBaseScript oldScript = unloadScript(newScript, true);// 卸载老脚本
            if (loadScript(newScript, true)) {// 加载新脚本
                LOGGER.info("脚本【{}】重载reloadScript回调", newScript.getName());
                newScript.reloadScript(oldScript);
                LOGGER.info("重载脚本【{}】成功", scriptName);
                return true;
            }
        } catch (ClassNotFoundException e) {
            LOGGER.error("脚本【{}】重载失败", scriptName);
            return false;
        } catch (InstantiationException e) {
            LOGGER.error("脚本【{}】重载失败", scriptName);
            return false;
        } catch (IllegalAccessException e) {
            LOGGER.error("脚本【{}】重载失败", scriptName);
            return false;
        }
        LOGGER.error("脚本【{}】重载失败", scriptName);
        return false;
    }

    /**
     * 重载所有的脚本
     */
    @SuppressWarnings("unchecked")
    public static synchronized boolean reloadAllScripts() {
        List<IBaseScript> registeredScripts = getLoadedScripts();
        if (registeredScripts.isEmpty()) {
            return false;
        }
        Map<String, IBaseScript> tempScripts = new ConcurrentHashMap<String, IBaseScript>();
        Map<String, Map<String, IBaseScript>> tempAllEventScripts = new ConcurrentHashMap<String, Map<String, IBaseScript>>();
        LOGGER.info("重载所有的脚本开始");
        FileSystemClassLoader loader = new FileSystemClassLoader(scriptRootPath, scriptJarFileName);
        loader.addAllDirectLoadClasses(registeredScripts);
        Iterator<IBaseScript> it = registeredScripts.iterator();
        while (it.hasNext()) {
            IBaseScript oldScript = it.next();
            try {
                Class<IBaseScript> clz = (Class<IBaseScript>) loader.loadClass(oldScript.getName());
                IBaseScript newScript = clz.newInstance();
                if (!loadScript(newScript, tempScripts, tempAllEventScripts, false)) {// 加载到临时的map里面，先不回调加载函数，避免加载失败时影响到某些系统
                    LOGGER.error("脚本【{}】重载失败", oldScript.getName());
                    return false;
                }
            } catch (ClassNotFoundException e) {
                LOGGER.error("脚本【{}】重载失败", oldScript.getName());
                return false;
            } catch (InstantiationException e) {
                LOGGER.error("脚本【{}】重载失败", oldScript.getName());
                return false;
            } catch (IllegalAccessException e) {
                LOGGER.error("脚本【{}】重载失败", oldScript.getName());
                return false;
            }
        }

        // 所有的脚本已经重载成功了，开始回调相应的函数
        it = registeredScripts.iterator();
        while (it.hasNext()) {
            IBaseScript oldScript = it.next();
            IBaseScript newScript = tempScripts.get(oldScript.getName());
            LOGGER.info("脚本【{}】卸载unloadScript回调", oldScript.getName());
            oldScript.unloadScript();// 老脚本卸载回调
            LOGGER.info("脚本【{}】加载onloadScript回调", newScript.getName());
            newScript.onloadScript();// 新脚本加载回调
            LOGGER.info("脚本【{}】重载reloadScript回调", newScript.getName());
            newScript.reloadScript(oldScript);// 新脚本重载回调
        }
        if (inMainScript) {// 脚本原来在主存储中，则重载脚本时放入辅助存储中
            viceScripts = tempScripts;
            viceAllEventScripts = tempAllEventScripts;
            inMainScript = false;
        } else {
            mainScripts = tempScripts;
            mainAllEventScripts = tempAllEventScripts;
            inMainScript = true;
        }
        LOGGER.info("重载所有的脚本成功");
        return true;
    }
}
